home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CD Ware Multimedia 1995 May
/
cd Ware (Juegos) Epimundo.iso
/
DOS
/
PRGMMING
/
M2PROTOS.ZIP
/
QCBPLUS.MOD
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
Macintosh to JP
NeXTSTEP
RISC OS/Acorn
Shift JIS
UTF-8
Wrap
Modula Implementation
|
1993-12-12
|
41.4 KB
|
1,280 lines
(*# call(o_a_copy => off) *)
(*%F _fcall *)
(*# call(seg_name => null) *)
(*%E *)
(*# module(implementation=>on) *)
(*# data(seg_name => null) *)
(*# data(const_assign => on) *)
IMPLEMENTATION MODULE QCbplus;
(* This JPI Modula-2 module is part of *)
(* QC -- a communications program *)
(* by Carl Neiburger *)
(* 169 N. 25th St.*)
(* San Jose, Calif. 95116 *)
(* CompuServe No. 72336,2257 *)
FROM CRC IMPORT ChkProc, DoCRC, DoBCks;
FROM Str IMPORT Append, CHARSET, CardToString, Concat, Length;
FROM QCcomm IMPORT CommRdData, CommRdDataTest, CommWrStr, CommWrData,
ComTimedOut, ComAbort, etx, cr, dle, enq, etx, nak;
FROM NFIO IMPORT Close, Create, EOF, Erase, File, Size, OK, Open, Exists,
PathStr, RdBin, Rename, SeekEOF, WrBin;
FROM QCdisp IMPORT BPlus, DataBytes, DataLeft, DataRegisters, DisplayData,
AbortMsg, TotalBytes, Packets, QCDefPtr, ShowErrorType, StartDisplay,
ShowPacketSize, ShowTimeLeft, StopDisplay, IncrDataBytes, ShowFileName,
Errs, CloseError, CreateError, TimeoutMsg, WriteErrorMsg, StatusMessage,
OpenError, PromptForString, Yes, ShowTransferTime, PressKey, FlushLog,
ShowTransferType, UpdateData;
FROM QCproto IMPORT ChoosePath;
FROM UTIL IMPORT NUMSET, SBITSET, str5, str10, str11, str80;
FROM Lib IMPORT Fill, Move, ScanR;
FROM FioAsm IMPORT DiskFree, GetDrive;
FROM RBvideo IMPORT Delay, WrStr;
FROM Storage IMPORT ALLOCATE, DEALLOCATE;
FROM Timer IMPORT StartTimer, ForTransfer, ForPacket;
FROM QCshell IMPORT GifTempName, ShowSaveGif;
FROM MiscAsm IMPORT HI;
CONST
MaxBufSize = 1032; (* Largest data block we can handle *)
MaxSA = 2; (* Maximum number of waiting packets *)
AFailureMsg = 'AAborted by user';
TYPE
QSArray = ARRAY [0..7] OF SHORTCARD;
TransParamRec = RECORD
WS : SHORTCARD; (* Window Send *)
WR : SHORTCARD; (* Window Receive *)
BS : SHORTCARD; (* Block Size *)
CM : BOOLEAN; (* Check Method *)
DQ : SHORTCARD; (* Old quote set *)
xx : BYTE; (* No transport layer here *)
QS : QSArray; (* Quote Set *)
(* The next 3 Parameters are FOR the B Plus File Transfer Application *)
DR : SHORTCARD; (* Download Recovery Option *)
UR : SHORTCARD; (* Upload Recovery Option *)
FI : SHORTCARD; (* File Information Option *)
END;
TransParamPtr = POINTER TO TransParamRec;
BPtr = POINTER TO ARRAY [0..MaxBufSize] OF SHORTCARD;
BufRec = RECORD
seq : CARDINAL; (* Packet's sequence number *)
num : CARDINAL; (* Number of bytes in packet *)
buf : BPtr; (* Actual packet data *)
END;
VAR
seqNum : CARDINAL; (* Current Sequence Number - init by TermENQ *)
checksum : CARDINAL; (* May hold CRC *)
chkInit : CARDINAL; (* Initial checksum or CRC *)
UpdChk : ChkProc; (* Do CRC or Checksum *)
His : TransParamRec; (* Initiator's Parameters *)
Our : TransParamRec; (* Negotiated Parameters *)
BPlusOn : BOOLEAN; (* TRUE if B Plus in effect *)
UseCRC : BOOLEAN; (* TRUE if CRC in effect *)
SpecialQuoting : BOOLEAN; (* TRUE to use SpecialQuoteSet *)
SpecialQuoteSet : QSArray; (* User's specified Quote Set *)
BufferSize : CARDINAL; (* Our.BS * 4 *)
SAMax : CARDINAL; (* 1 IF SA NOT enabled, ELSE MaxSA *)
SAErrors : CARDINAL; (* # OF times SSendData called *)
QuoteTable : ARRAY [0..255] OF SHORTCARD; (* The quoting table *)
FileType : str10; (* used to pass info to datadisp *)
MsgStr : str80; (* general purpose string *)
DataFile : File;
DefPtr : TransParamPtr;
CONST
DQfull = QSArray(
0FFH, 0FFH, 0FFH, 0FFH,
0FFH, 0FFH, 0FFH, 0FFH
);
DQdefault = QSArray(
14H, 00H, 0D4H, 00H, (* ETX ENQ DLE XON XOFF NAK *)
00H, 00H, 00H, 00H
);
DQminimal = QSArray(
14H, 00H, 0D4H, 00H, (* ETX ENQ DLE XON XOFF NAK *)
00H, 00H, 00H, 00H
);
DQextended = QSArray(
14H, 00H, 0D4H, 00H, (* ETX ENQ DLE XON XOFF NAK *)
00H, 00H, 50H, 00H (* XON XOFF *)
);
Def = TransParamRec(
(* WS *) 1, (* I can Send 2 Packets ahead *)
(* WR *) 1, (* I can receive single Send-ahead *)
(* BS *) 4,
(* CM *) TRUE, (* I CAN handle CRC *)
(* DQ *) 2, (* I need extended quote set *)
(* (including the `Tf' Packet *)
(* xx *) 0,
QSArray(DQextended),
(* DR *) 1, (* I CAN handle Download Recovery *)
(* UR *) 0, (* I CANNOT handle Upload Recovery *)
(* FI *) 1); (* I can handle File Information *)
FirstTPR = TransParamRec(
(* WS *) 0, (* No send ahead *)
(* WR *) 0, (* ditto *)
(* BS *) 4,
(* CM *) FALSE, (* NO CRC *)
(* DQ *) 2, (* I need extended quote set *)
(* xx *) 0,
QSArray(DQextended),
(* DR *) 0, (* NO Download Recovery *)
(* UR *) 0, (* NO Upload Recovery *)
(* FI *) 0); (* NO File Information *)
PROCEDURE UpdateQuoteTable (QuoteSet : QSArray);
(* Sets the i-th entry OF QuoteTable to the necessary quoting character
according to the i-th bit of the supplied quote set.*)
VAR
i, j, k : CARDINAL;
b, c : SHORTCARD;
BEGIN
k := 0;
c := 40H;
FOR i := 0 TO 7 DO
IF i = 4 THEN (* Switch to upper control set *)
c := 60H;
k := 128;
END;
b := QuoteSet [i];
FOR j := 7 TO 0 BY -1 DO
IF j IN SBITSET(b) THEN
QuoteTable [k] := c
END;
INC(c);
INC(k)
END;
END;
END UpdateQuoteTable ;
PROCEDURE QuoteThis (Value: SHORTCARD);
(* Sets SpecialQuoting TRUE to use the special quote set. *)
(* If Value = 0FFH, the special quote set is restored to default. *)
VAR i : CARDINAL;
BEGIN
IF Value IN NUMSET{00H..1FH,80H..9FH} THEN
IF Value > 1FH THEN
i := 4;
Value := Value MOD 20H
ELSE
i := 0
END;
INC(i, ORD(Value DIV 8)); (* = index into SpecialQuoteSet *)
INCL( SBITSET(SpecialQuoteSet[i]), 7 - ORD(Value MOD 8) );
SpecialQuoting := TRUE;
ELSIF Value = 0FFH THEN (* Restore the Quote Set? *)
SpecialQuoteSet := DQextended;
SpecialQuoting := FALSE;
END;
END QuoteThis;
PROCEDURE TermENQ;
(* called when the terminal emulator receives an <ENQ> from the host.
It initializes for B Protocol and tells the host that we support B Plus. *)
CONST TermEnqResp = CHR(dle) + '++' + CHR(dle) + '0';
VAR cks : CARDINAL;
BEGIN
seqNum := 0;
BufferSize := 512; (* default *)
Our := FirstTPR;
BPlusOn := FALSE; (* NOT B Plus Protocol *)
UseCRC := FALSE; (* NOT CRC *)
chkInit := 0;
UpdChk := DoBCks;
SAMax := 1; (* Single Packet Send *)
SAErrors := 0; (* Reset counter *)
Fill( ADR(QuoteTable), SIZE(QuoteTable), 0);
UpdateQuoteTable (DQextended);
cks := CommWrStr( TermEnqResp );
END TermENQ;
PROCEDURE TermEscI (EscIResponse : ARRAY OF CHAR);
(* called when <ESC><I> is received.
CompuServe now recognizes the string ",+xxxx" as the final field.
This provides a checksum (xxxx being the ASCII decimal representation of the
sum of all characters in the response string from # to +. The checksum
eliminates the need for retransmission and comparison of the response. *)
VAR t : str5; cks : CARDINAL;
BEGIN
cks := CommWrStr( EscIResponse ) + CommWrStr( ',+' );
CardToString( VAL(LONGCARD, cks ), t, 0, 0C );
cks := CommWrStr( t );
CommWrData (cr);
END TermEscI;
PROCEDURE DleBSeen;
(* called from the main program when <DLE> B is received. This calls
ReadPacket and then calls the appropriate routine to handle the packet. *)
CONST MaxErrors = 10;
VAR
Len, (* used in decoding 'T' packet *)
RSize : CARDINAL; (* Bytes in receiver buffer *)
Ch : SHORTCARD; (* current character *)
SABuf : ARRAY[0..MaxSA] OF BufRec;
RBuf : BPtr;
PacketReceived, (* True if a packet was received *)
Quoted : BOOLEAN; (* True if ctrl character was quoted *)
SANextToACK : CARDINAL; (* Which SABuf is waiting for an ACK *)
SANextToFill : CARDINAL; (* Which SABuf is ready for new data *)
SAWaiting : CARDINAL; (* Number of SABufs waiting for ACK *)
AbortRequest : BOOLEAN; (* True if keyboard abort requested *)
Aborting : BOOLEAN; (* True if aborting the transfer *)
AbortCount : CARDINAL; (* Number of times checkAbort() returns TRUE *)
FatalAbort : BOOLEAN; (* True if AbortCount exceeds AbortMax *)
FileName : PathStr; (* pathname *)
ResumeFlag : BOOLEAN; (* True if attempting a DOW resume *)
RFileSize, (* Size of file being received *)
FileLength : LONGCARD; (* for download resumption *)
PROCEDURE SendQuotedByte (ch : BYTE );
BEGIN
IF QuoteTable [ORD(ch)] <> 0 THEN
CommWrData (dle);
CommWrData (QuoteTable [ORD(ch)]);
ELSE
CommWrData (ch);
END
END SendQuotedByte;
PROCEDURE SendACK;
BEGIN
CommWrData (dle);
CommWrData ( SHORTCARD(seqNum) + SHORTCARD('0'));
END SendACK;
PROCEDURE SendNAK;
BEGIN
INC(DataRegisters[TRUE, Errs]);
DisplayData( Errs, TRUE );
CommWrData (nak);
END SendNAK;
PROCEDURE SendENQ;
BEGIN
CommWrData (enq);
CommWrData (enq);
END SendENQ;
PROCEDURE ReadByte () : BOOLEAN;
VAR t, dat : CARDINAL;
BEGIN
IF Aborting THEN
t := 10
ELSE
t := 30
END;
dat := CommRdDataTest ( t );
Ch := VAL(SHORTCARD, dat );
CASE dat OF
ComTimedOut : RETURN FALSE;
| ComAbort : AbortRequest := TRUE;
INC (AbortCount);
IF AbortCount >= AbortMax THEN
FatalAbort := TRUE;
END;
RETURN FALSE;
END; (* CASE *)
RETURN TRUE;
END ReadByte;
PROCEDURE ReadQuotedByte () : BOOLEAN;
VAR t, dat : CARDINAL;
BEGIN
Quoted := FALSE;
IF Aborting THEN
t := 10
ELSE
t := 30
END;
dat := CommRdData ( t );
Ch := VAL(SHORTCARD, dat );
CASE dat OF
ComTimedOut : RETURN FALSE;
|ComAbort : AbortRequest := TRUE;
INC (AbortCount);
IF AbortCount >= AbortMax THEN
FatalAbort := TRUE;
END;
RETURN FALSE;
END; (* CASE *)
IF Ch = dle THEN
IF NOT ReadByte() THEN
RETURN FALSE;
END;
IF Ch < 60H THEN
Ch := Ch MOD 20H;
ELSE
Ch := Ch MOD 20H + 80H
END;
Quoted := TRUE;
END;
RETURN TRUE;
END ReadQuotedByte;
PROCEDURE ShowFailure;
BEGIN
SendACK;
CASE CHR( RBuf^[1] ) OF
'A': MsgStr := 'Host aborting transfer.';
|'C': MsgStr := 'Host out of memory; aborting transfer.';
|'E','N','S': MsgStr := 'Processing failure; host aborting transfer.';
|'I': MsgStr := 'Input-output error; host aborting transfer.';
|'M': MsgStr := 'File requested is missing; host aborting transfer.';
|'r': MsgStr := 'Transfer resume failure; aborting.';
END;
StatusMessage ( MsgStr, FALSE );
END ShowFailure;
PROCEDURE SendFailure (Reason : ARRAY OF CHAR ); FORWARD;
PROCEDURE ReadPacket (LeadInSeen, FromSendPacket : BOOLEAN) : BOOLEAN;
(* LeadInSeen is TRUE if the <DLE><B> has been seen already.
FromSendPacket is TRUE if called from SendPacket; if it is true,
ReadPacket returns on first error detected.
ReadPacket returns TRUE if packet is available from host. *)
TYPE
ReceiveStateType = (
RGetDle,
RGetB,
RGetSeq,
RGetData,
RGetCheck,
RSendAck,
RTimedOut,
RError,
RSuccess );
VAR
State : ReceiveStateType;
PacketNum,
errors,
newCks,
i : CARDINAL;
NAKSent : BOOLEAN; (* TRUE IF <NAK> was sent *)
BEGIN
IF PacketReceived THEN (* See if Packet was picked up on a call to *)
PacketReceived := FALSE; (* GetACK *)
RETURN TRUE;
END;
NAKSent := FALSE;
errors := 0;
IF LeadInSeen THEN
State := RGetSeq (* Start off on the correct foot *)
ELSE
State := RGetDle
END;
LOOP
CASE (State) OF
RGetDle : IF AbortRequest AND NOT Aborting THEN
StatusMessage (AbortMsg, FALSE);
SendFailure (AFailureMsg);
RETURN FALSE;
END;
IF NOT ReadByte() THEN
State := RTimedOut
ELSE
CASE Ch OF
dle: State := RGetB;
|enq: State := RSendAck;
END
END;
|RGetB : Fill ( RBuf, BufferSize, 0);
IF NOT ReadByte() THEN
State := RTimedOut
ELSE CASE Ch OF
SHORTCARD ('B'): State := RGetSeq;
|enq: State := RSendAck;
(* |SHORTCARD(';'): State := RGetDle; *)
ELSE State := RGetDle
END END;
|RGetSeq : IF NOT ReadByte() THEN
State := RTimedOut
ELSIF Ch = enq THEN
State := RSendAck;
ELSE
PacketNum := ORD(Ch - SHORTCARD ('0'));
checksum := UpdChk ( ADR(Ch), 1, chkInit );
i := 0;
State := RGetData;
END;
|RGetData : IF NOT ReadQuotedByte() THEN
State := RTimedOut
ELSIF (Ch = etx) AND NOT Quoted THEN
checksum := UpdChk ( ADR(Ch), 1, UpdChk (RBuf, i, checksum) );
State := RGetCheck;
ELSIF i <= MaxBufSize THEN
RBuf^[i] := Ch;
INC(i);
ELSE
StatusMessage ('Buffer overrun.', FALSE);
State := RGetDle;
END;
|RGetCheck :
IF ReadQuotedByte() THEN
IF BPlusOn AND UseCRC THEN (* ??? *)
checksum := UpdChk (ADR(Ch), 1, checksum );
IF ReadQuotedByte() THEN
checksum := UpdChk (ADR(Ch), 1, checksum );
newCks := 0;
ELSE
newCks := CARDINAL(BITSET(checksum) / BITSET(0FFH))
END;
ELSE
newCks := ORD( Ch )
END;
IF RBuf^[0] = SHORTCARD('F') THEN (* Failure Packet *)
ShowFailure;
State := RSuccess (* is accepted regardless *)
ELSIF (PacketNum = seqNum) THEN (* Watch for duplicate *)
IF (CHR (RBuf^[0]) = 'T') AND (CHR (RBuf^[1]) = 'C') THEN
RETURN TRUE (* Duplicate TC; D-d-d-dat's all folks *)
ELSE
State := RSendAck (* Simply ACK it *)
END
ELSIF PacketNum = (seqNum + 1) MOD 10 THEN
IF newCks = checksum THEN
State := RSuccess
ELSE
StatusMessage('Bad checksum.', FALSE );
State := RError
END
ELSE
State := RGetDle; (* Bad sequence number *)
END;
ELSE
State := RTimedOut
END;
|RTimedOut :
IF AbortRequest THEN
State := RGetDle;
ELSE
StatusMessage (TimeoutMsg, FALSE);
State := RError;
END;
|RError :
INC (errors);
IF (errors > MaxErrors) OR FromSendPacket OR FatalAbort THEN
RETURN FALSE;
END;
IF NOT NAKSent OR NOT BPlusOn THEN
NAKSent := TRUE;
SendNAK;
END;
State := RGetDle;
|RSendAck :
IF NOT Aborting THEN
SendACK;
END;
State := RGetDle; (* wait for the next packet *)
|RSuccess :
DisplayData ( TotalBytes, TRUE);
DisplayData ( TotalBytes, FALSE );
IF NOT Aborting THEN
seqNum := PacketNum
END;
RSize := i;
INC (DataRegisters[ TRUE, Packets ]);
DisplayData ( Packets, TRUE );
RETURN TRUE;
END; (* CASE *)
END; (* LOOP *)
END ReadPacket;
PROCEDURE SendData (BufferNumber : CARDINAL);
VAR i : CARDINAL; ch: SHORTCARD;
BEGIN
WITH SABuf[BufferNumber] DO
checksum := chkInit;
CommWrData (dle);
CommWrData ('B');
ch := SHORTCARD(seq) + SHORTCARD('0');
CommWrData ( ch );
checksum := UpdChk ( ADR(ch), 1, checksum );
FOR i := 0 TO num DO
SendQuotedByte (buf^[i]);
END;
checksum := UpdChk (buf, num+1, checksum );
CommWrData (etx);
ch := etx;
checksum := UpdChk ( ADR(ch), 1, checksum );
IF UseCRC THEN
SendQuotedByte (VAL(SHORTCARD,HI(checksum)) );
END;
SendQuotedByte (VAL(SHORTCARD,checksum));
END;
END SendData;
PROCEDURE ReSync () : SHORTCARD;
(* called to restablish syncronism with the remote by Sending <ENQ><ENQ> and
waiting for <DLE><d><DLE><d>, ignoring everything else. Return is ORD('T')
on time out, `B` IF <DLE><B>, 'E' if <ENQ>, the digit <d> if successful. *)
TYPE
ReSyncStateType = (
GetFirstDle,
GetFirstDigit,
GetSecondDle,
GetSecondDigit);
VAR
State : ReSyncStateType;
Digit1 : SHORTCARD;
BEGIN
SendENQ; (* Send <ENQ><ENQ> *)
State := GetFirstDle;
LOOP
CASE (State) OF
GetFirstDle : IF NOT ReadByte() THEN
RETURN SHORTCARD('T')
END;
CASE Ch OF
dle: State := GetFirstDigit;
|enq: RETURN SHORTCARD('E'); (* totally out of synch *)
END;
|GetFirstDigit : IF NOT ReadByte() THEN
RETURN SHORTCARD('T')
END;
CASE CHR(Ch) OF
'0'..'9': Digit1 := Ch;
State := GetSecondDle;
|'B': RETURN Ch;
END;
|GetSecondDle : IF NOT ReadByte() THEN
RETURN SHORTCARD('T')
END;
IF Ch = dle THEN
State := GetSecondDigit;
END;
|GetSecondDigit : IF NOT ReadByte() THEN
RETURN SHORTCARD('T')
END;
CASE CHR(Ch) OF
'0'..'9': IF Digit1 = Ch THEN
RETURN Ch;
END;
Digit1 := Ch;
State := GetSecondDle;
|'B': RETURN Ch;
ELSE State := GetSecondDle
END;
END; (* CASE *)
END; (* LOOP *)
END ReSync;
PROCEDURE GetACK (): BOOLEAN;
(* called to wait until the host ACKs SABuf indicated by SANextToACK *)
TYPE
SendStateType = (
SGetDle,
SGetNum,
SHaveACK,
SGetPacket,
SSkipPacket,
STimedOut,
SError,
SSendNak,
SSendEnq,
SSendData );
VAR
State : SendStateType;
PacketNum,
errors,
i,
SAIndex : CARDINAL;
SentEnq : BOOLEAN;
PROCEDURE GotNak;
BEGIN
INC(DataRegisters[FALSE, Errs]);
DisplayData( Errs, FALSE );
State := SSendEnq
END GotNak;
BEGIN
PacketReceived := FALSE;
errors := 0;
SentEnq := FALSE;
State := SGetDle;
LOOP
CASE (State) OF
SGetDle :
IF AbortRequest AND NOT Aborting THEN
StatusMessage (AbortMsg, FALSE);
SendFailure (AFailureMsg);
RETURN FALSE;
END;
IF NOT ReadByte() THEN
State := STimedOut
ELSE
CASE Ch OF
dle: State := SGetNum;
|nak: GotNak;
|enq: SendACK; (* DIAG *)
StatusMessage('RESYNC ERROR 1', FALSE ); (* DIAG *)
SendFailure ('SProtocol sequence failure');
RETURN FALSE; (* totally out of synch *)
|etx: State := SSendNak;
END;
END;
|SGetNum :
IF NOT ReadByte() THEN
State := STimedOut
ELSE CASE Ch OF
SHORTCARD('0')..
SHORTCARD('9'): State := SHaveACK
|SHORTCARD('B'): IF Aborting THEN
State := SSkipPacket;
ELSE
State := SGetPacket
END;
|nak: GotNak;
|SHORTCARD(';'):
State := SGetDle; (* WACK (Wait Acknowledge) *)
ELSE State := SGetDle;
END END;
|SGetPacket :
IF ReadPacket (TRUE, TRUE) THEN
PacketReceived := TRUE;
IF RBuf^[0] = SHORTCARD('F') THEN (* Failure Packet *)
ShowFailure;
RETURN FALSE;
END;
(* State := SGetDle; Stay here to find the ACK *)
SANextToACK := (SANextToACK + 1) MOD (MaxSA + 1);
DEC( SAWaiting );
RETURN TRUE
ELSIF (AbortRequest AND NOT Aborting) OR FatalAbort THEN
RETURN FALSE
ELSE
State := SGetDle; (* Receive failed; keep watching FOR ACK *)
END;
|SSkipPacket : (* Skip an incoming Packet *)
IF NOT ReadByte() THEN
State := STimedOut
ELSIF Ch = etx THEN
IF NOT ReadQuotedByte() THEN (* Get Checksum or CRC *)
State := STimedOut
ELSIF NOT UseCRC THEN
State := SGetDle
ELSIF NOT ReadQuotedByte() THEN
State := STimedOut
ELSE
State := SGetDle
END;
END;
|SHaveACK :
PacketNum := ORD(Ch - SHORTCARD('0'));
IF SABuf[SANextToACK].seq = PacketNum THEN
(* This is the one we're waiting for *)
SANextToACK := (SANextToACK + 1) MOD (MaxSA + 1);
DEC( SAWaiting );
IF SAErrors > 0 (* Apply heuristic to control *)
THEN DEC (SAErrors); (* Upload Performance degradation *)
END;
RETURN TRUE;
END;
IF (SABuf [ (SANextToACK + 1) MOD (MaxSA + 1) ].seq = PacketNum)
AND (SAWaiting = 2) THEN (* Must have missed an ACK *)
SANextToACK := (SANextToACK + 2) MOD (MaxSA + 1);
DEC ( SAWaiting, 2 );
IF SAErrors > 0 THEN
DEC (SAErrors)
END;
RETURN TRUE;
END;
IF SABuf [SANextToACK].seq = (PacketNum + 1) MOD 10 THEN
IF SentEnq THEN
State := SSendData (* Remote missed first packet*)
ELSE
State := SGetDle (* Duplicate ACK *)
END;
ELSE (* WHILE aborting, *)
IF NOT Aborting THEN
State := STimedOut (* ignore ACKs *)
ELSE
State := SGetDle (* which are NOT for failure Packet.*)
END;
END;
SentEnq := FALSE;
|STimedOut :
State := SSendEnq;
|SSendNak :
INC (errors);
IF (errors > MaxErrors) THEN
StatusMessage('Too many errors; Aborting.', FALSE);
END;
IF (errors > MaxErrors) OR FatalAbort THEN
RETURN FALSE;
END;
SendNAK;
State := SGetDle;
|SSendEnq :
INC (errors);
IF (errors > MaxErrors) OR (Aborting AND (errors > 3)) THEN
StatusMessage('Too many errors; Aborting.', FALSE);
RETURN FALSE;
END;
Ch := ReSync();
CASE CHR(Ch) OF
'T': State := SGetDle;
|'B': IF Aborting THEN
State := SSkipPacket
ELSE
State := SGetPacket
END;
|'E': StatusMessage('RESYNC ERROR 2', FALSE ); (* DIAG *)
RETURN FALSE;
ELSE State := SHaveACK;
END;
SentEnq := TRUE;
|SSendData :
INC (SAErrors, 3);
IF SAErrors >= 12 THEN
SAMax := 1
END;
SAIndex := SANextToACK;
FOR i := 1 TO SAWaiting DO
SendData (SAIndex);
SAIndex := (SAIndex + 1) MOD (MaxSA + 1);
END;
State := SGetDle;
SentEnq := FALSE;
END; (* CASE *)
END; (* LOOP *)
END GetACK;
PROCEDURE SendPacket (size : CARDINAL) : BOOLEAN;
BEGIN
WHILE (SAWaiting >= SAMax) DO
IF NOT GetACK() THEN
RETURN FALSE; (* Allow for possible drop out of Send Ahead *)
END
END;
seqNum := (seqNum + 1) MOD 10;
SABuf [SANextToFill].seq := seqNum;
SABuf [SANextToFill].num := size;
SendData (SANextToFill);
SANextToFill := (SANextToFill + 1) MOD (MaxSA + 1);
INC( SAWaiting );
INC (DataRegisters[ FALSE, Packets ]);
DisplayData ( Packets, FALSE );
RETURN TRUE
END SendPacket;
PROCEDURE SAFlush () : BOOLEAN;
(*called after sending last packet to get host's ACKs on outstanding packets.*)
BEGIN
WHILE SAWaiting > 0 DO
IF NOT GetACK() THEN
RETURN FALSE;
END;
RETURN TRUE;
END;
END SAFlush;
PROCEDURE SendFailure (Reason : ARRAY OF CHAR );
BEGIN
SANextToACK := 0;
SANextToFill := 0;
SAWaiting := 0;
Aborting := TRUE; (* Required by GetACK *)
WITH SABuf [0] DO
buf^[0] := SHORTCARD ('F');
Move( ADR(Reason), ADR(buf^[1]), Length(Reason) )
END;
IF SendPacket (Length (Reason)) AND
SAFlush() THEN (* wait for ACK *)
END
END SendFailure;
PROCEDURE SendFile (name : PathStr );
(* called to Send a file to the host *)
VAR n : CARDINAL;
BEGIN
DataFile := Open(name);
IF DataFile = MAX( CARDINAL ) THEN
StatusMessage (OpenError, TRUE);
SendFailure ('MFile not found');
RETURN
END;
DataRegisters[ FALSE, DataLeft ] := Size (DataFile);
StartTimer(ForTransfer);
StartTimer(ForPacket);
ShowTimeLeft( FALSE );
WHILE NOT EOF(DataFile) DO
SABuf[SANextToFill].buf^[0] := SHORTCARD('N');
n := RdBin (DataFile, SABuf[SANextToFill].buf^[1], BufferSize);
IF NOT OK THEN
SendFailure ('EFile read failure');
StatusMessage ('Read error. Aborting', TRUE);
RETURN
END;
IF NOT SendPacket (n) THEN
RETURN
END;
IncrDataBytes( n, FALSE );
END;
Close (DataFile);
SABuf [SANextToFill].buf^[0] := SHORTCARD ('T');
SABuf [SANextToFill].buf^[1] := SHORTCARD ('C');
IF SendPacket (2) AND SAFlush() THEN END;
END SendFile;
PROCEDURE DoTransportParameters;
(* Called when a '+' packet is received. Sends our default B Plus parameters,
sets Our.xx parameters to minimum of host's and default parameters. *)
VAR
QuoteSetPresent : BOOLEAN;
PROCEDURE PickMin( A, B: BYTE): BYTE;
BEGIN
IF A < B THEN RETURN A END;
RETURN B
END PickMin;
BEGIN
IF SpecialQuoting THEN
Our.QS := SpecialQuoteSet
ELSE
Our.QS := DQextended;
END;
IF AutoResume THEN
DefPtr^.DR := 2 (* Set Download Resume according to *)
ELSE (* user's preference *)
DefPtr^.DR := 1
END;
Move ( ADR( RBuf^[1] ), ADR(His), 17 ); (* Initiator's parameters *)
QuoteSetPresent := RSize >= 14;
WITH SABuf [SANextToFill] DO
buf^[0] := SHORTCARD('+'); (* Prepare to return Our own parameters *)
Move ( ADR(Def), ADR( buf^[1] ), 17 );
END;
UpdateQuoteTable (DQfull); (* Send the + Packet under full quoting *)
IF NOT SendPacket (17) THEN
RETURN
END;
IF SAFlush() THEN (* Wait for host's ACK on Our Packet *)
Our.WR := PickMin( His.WS, DefPtr^.WR );
Our.WS := PickMin( His.WR, DefPtr^.WS );
Our.BS := PickMin( His.BS, DefPtr^.BS );
Our.CM := His.CM AND DefPtr^.CM;
Our.DR := PickMin( His.DR, DefPtr^.DR );
Our.UR := PickMin( His.UR, DefPtr^.UR );
Our.FI := PickMin( His.FI, DefPtr^.FI );
IF Our.BS = 0 THEN
Our.BS := 4 (* Default *)
END;
BufferSize := ORD(Our.BS) * 128;
BPlusOn := TRUE;
UseCRC := Our.CM;
IF UseCRC THEN
UpdChk := DoCRC;
chkInit := 0FFFFH
END;
IF Our.WS <> 0 THEN
SAMax := MaxSA
END;
END;
Fill( ADR(QuoteTable), SIZE(QuoteTable), 0);
UpdateQuoteTable (Our.QS); (* Restore Our Quoting Set *)
IF QuoteSetPresent THEN
UpdateQuoteTable (His.QS); (* Insert Initiator's Quote Set *)
END;
END DoTransportParameters;
PROCEDURE CheckKeep ( Name : ARRAY OF CHAR );
(* Called from ReceiveFile when a fatal error occurs to ask if file
should be retained *)
VAR
Retain : BOOLEAN;
BEGIN
Close (DataFile);
IF (NOT AutoResume) OR (NOT BPlusOn) OR (Our.DR = 0) THEN
Concat( MsgStr, 'Do you wish to retain the partial ', Name );
Append( MsgStr, '? ');
Retain := Yes (MsgStr)
ELSE
Retain := TRUE
END;
IF Retain THEN
StatusMessage ('File retained.', TRUE);
ELSE
Erase (Name);
StatusMessage ('File erased.', TRUE);
END;
END CheckKeep;
PROCEDURE FileCreated( Name : ARRAY OF CHAR ): BOOLEAN;
BEGIN
DataFile := Create( Name );
IF DataFile = MAX ( CARDINAL ) THEN
StatusMessage(CreateError, FALSE);
SendFailure ('CCannot create file');
RETURN FALSE;
END;
SendACK;
RETURN TRUE
END FileCreated;
PROCEDURE ReceiveData (Name : ARRAY OF CHAR): BOOLEAN;
(* called by ReceiveFile or ReceiveGIF *)
VAR Drive : SHORTCARD;
ClusterSize : CARDINAL;
PROCEDURE ReceiveFileSize;
(* called from ReceiveFile when TI Packet is received to process information *)
VAR
i : CARDINAL;
BEGIN
i := 4; (* Skip data type and compression flag *)
WHILE ( i < RSize ) AND NOT( CHR(RBuf^[i]) IN CHARSET{'0'..'9'} ) DO
INC(i)
END;
RFileSize := 0;
WHILE ( i < RSize ) AND ( CHR(RBuf^[i]) IN CHARSET{'0'..'9'} ) DO
RFileSize := RFileSize*10 +
VAL(LONGCARD, SHORTCARD(RBuf^[i])-SHORTCARD('0') );
INC(i)
END;
END ReceiveFileSize;
BEGIN
StartTimer(ForPacket);
StartTimer(ForTransfer);
LOOP
IF ReadPacket (FALSE, FALSE) THEN
CASE CHR (RBuf^[0]) OF
'N' : IF ResumeFlag THEN
StatusMessage ('Resuming Download', FALSE);
ResumeFlag := FALSE;
END;
WrBin (DataFile, RBuf^[1], RSize - 1 );
IF NOT OK THEN
StatusMessage (WriteErrorMsg, FALSE);
SendFailure ('EWrite failure');
CheckKeep (Name);
RETURN FALSE;
END;
IncrDataBytes( RSize - 1, TRUE );
SendACK;
|'T' : CASE CHR(RBuf^[1]) OF
'C': Close (DataFile);
UpdateData;
IF NOT OK THEN
StatusMessage (CloseError, FALSE);
SendFailure ('EError during close');
CheckKeep (Name);
RETURN FALSE;
END;
SendACK;
RETURN TRUE;
|'I': SendACK;
ReceiveFileSize;
IF RFileSize > FileLength THEN
DataRegisters[TRUE,DataLeft] := RFileSize - FileLength;
ShowTimeLeft( TRUE );
END;
IF RFileSize > 0 THEN
IF Name[1] = ':' THEN
Drive := SHORTCARD(CAP(Name[0]))
- SHORTCARD('@');
ELSE
Drive := GetDrive()
END;
IF DiskFree(Drive, ClusterSize) < RFileSize THEN
StatusMessage('Insufficient disk space. Aborting', TRUE);
SendFailure ('CInsufficient disk space.');
Close (DataFile);
RETURN FALSE;
END;
StartTimer(ForPacket);
StartTimer(ForTransfer);
(* restart for more accurate estimate *)
DisplayData ( DataLeft, TRUE )
END;
|'f': IF AutoResume THEN (* host failed CRC check *)
Close (DataFile);
IF NOT FileCreated(Name) THEN
RETURN FALSE;
END;
IF Our.FI <> 0 THEN
DataRegisters[ TRUE, DataLeft ] := RFileSize;
END;
RFileSize := 0;
StatusMessage ('CRC check failed; overwriting file', FALSE);
ResumeFlag := FALSE;
DataRegisters[ FALSE, TotalBytes ] := 0; (* ??? *)
DataRegisters[ TRUE, TotalBytes ] := 0; (* ??? *)
END;
ELSE
StatusMessage ('Invalid termination packet. Aborting', FALSE);
SendFailure ('NInvalid T Packet');
CheckKeep (Name);
RETURN FALSE;
END;
|'F' : ShowFailure;
CheckKeep (Name);
RETURN FALSE;
END; (* CASE *)
ELSE (* ReadPacket *)
IF NOT Aborting THEN
StatusMessage('Download failed', FALSE)
END;
CheckKeep (Name);
RETURN FALSE;
END;
END; (* LOOP *)
END ReceiveData;
PROCEDURE ReceiveFile (Name : ARRAY OF CHAR);
(* called to receive a file from the host *)
VAR
PacketLen,
i, n : CARDINAL;
DowType : CHAR;
dummy : BOOLEAN;
BEGIN
DowType := 'D'; (* Assume normal downloading *)
RFileSize := 0;
FileLength := 0;
IF Exists(Name) THEN (* See if we can try automatic resume *)
IF (Our.DR > 1) AND AutoResume THEN
DowType := 'R' (* Remote supports `Tf', let's try it *)
ELSIF (Our.DR > 0) THEN
IF Yes('File exists. Do you wish to resume downloading?') THEN
DowType := 'R';
ELSE
StatusMessage ('File being overwritten.', FALSE)
END;
END;
END;
CASE DowType OF
'D': IF NOT FileCreated( Name ) THEN
RETURN;
END;
|'R' : DataFile := Open ( Name ); (* Resume download *)
IF DataFile = MAX ( CARDINAL ) THEN
StatusMessage(OpenError, FALSE);
SendFailure ('MFile not found');
RETURN;
END;
StatusMessage ('Calculating CRC', FALSE);
WITH SABuf [SANextToFill] DO (* ASSUMES CRC *)
checksum := 0FFFFH;
LOOP
n := RdBin (DataFile, buf^, BufferSize );
IF (n = 0) OR (NOT OK) THEN
EXIT
END;
checksum := UpdChk (buf, n, checksum )
END;
buf^[0] := SHORTCARD ('T');
buf^[1] := SHORTCARD ('r');
PacketLen := 2;
FileLength := Size (DataFile);
CardToString( FileLength, MsgStr, 0, 0C );
Append (MsgStr, ' ');
i := Length(MsgStr);
Move ( ADR(MsgStr), ADR(buf^[PacketLen]), i );
INC( PacketLen, i );
CardToString( VAL(LONGCARD, checksum), MsgStr, 0, 0C );
Append (MsgStr, ' ');
i := Length(MsgStr);
Move ( ADR(MsgStr), ADR(buf^[PacketLen]), i );
INC( PacketLen, i );
END; (* WITH *)
IF NOT SendPacket(PacketLen-1) OR NOT SAFlush() THEN
Close (DataFile); (* SendData Sends 0..size *)
RETURN;
END;
SeekEOF(DataFile); (* Ready to append *)
StatusMessage ('Host calculating CRC...', FALSE);
ResumeFlag := TRUE;
END; (* CASE *)
dummy := ReceiveData( Name );
END ReceiveFile;
PROCEDURE CreateBufs;
(* Must call at start of DleBSeen *)
VAR n : CARDINAL;
BEGIN
FOR n := 0 TO MaxSA DO
NEW( SABuf[n].buf )
END;
NEW ( RBuf );
END CreateBufs;
PROCEDURE ReleaseBufs;
(* Must call before returning from DleBSeen *)
VAR n : CARDINAL;
BEGIN
FOR n := 0 TO MaxSA DO
DISPOSE( SABuf[n].buf )
END;
DISPOSE( RBuf );
END ReleaseBufs;
PROCEDURE ReceiveGIF;
VAR GotIt : BOOLEAN;
BEGIN
PacketReceived := TRUE;
ResumeFlag := FALSE;
IF NOT FileCreated( GifTempName ) THEN
RETURN;
END;
GotIt := ReceiveData( GifTempName );
ShowTransferTime;
ReleaseBufs;
IF GotIt THEN
ShowSaveGif;
END;
END ReceiveGIF;
PROCEDURE TurnDisplayOn( FileName: ARRAY OF CHAR; Receiving: BOOLEAN );
BEGIN
FlushLog;
(* TempBytes := DataRegisters[ TRUE, TotalBytes ]; *)
StartDisplay( TRUE, BPlus, Receiving );
ShowFileName( FileName, Receiving );
ShowTransferType ( FileType );
(* DataRegisters[ TRUE, TotalBytes ] := TempBytes; *)
ShowErrorType(UseCRC);
IF BPlusOn THEN
ShowPacketSize( BufferSize );
IF Our.WS > 0 THEN
StatusMessage( 'Send-Ahead enabled', FALSE )
END
END;
END TurnDisplayOn;
BEGIN (* DleBSeen *)
SANextToACK := 0; (* Initialize variables *)
SANextToFill := 0;
SAWaiting := 0;
Aborting := FALSE;
AbortRequest := FALSE;
FatalAbort := FALSE;
AbortCount := 0;
PacketReceived := FALSE;
ResumeFlag := FALSE;
(* Establish data block size to keep time per packet at 4-5 seconds *)
CASE QCDefPtr^.baud OF
0 : DefPtr^.BS := 1; (* 300 *)
|1,2 : DefPtr^.BS := 4; (* 600, 1200 *)
|ELSE DefPtr^.BS := 8;
END; (* CASE *)
CreateBufs;
DataRegisters[ TRUE, TotalBytes ] := 2; (* DLE B *)
IF ReadPacket (TRUE, FALSE) THEN
CASE CHR (RBuf^[0]) OF
'T': CASE CHR (RBuf^[1]) OF
'D', 'U':;
|'C': CommWrData (cr);
(* SendACK; maybe duplicate completion *)
ReleaseBufs;
RETURN;
ELSE StatusMessage ('Unimplemented transfer function', TRUE);
SendFailure ('NUnimplemented transfer function');
ReleaseBufs;
RETURN;
END;
CASE CHR (RBuf^[2]) OF
'A': FileType := ' (ASCII)';|
'B': FileType := ' (Binary)';|
ELSE
StatusMessage ('Unimplemented file type', TRUE);
SendFailure ('NUnimplemented file type');
ReleaseBufs;
RETURN;
END;
Len := 3;
WHILE (RBuf^[Len] <> 0) AND (Len < RSize ) DO
INC(Len)
END;
DEC(Len, 3); (* length of name *)
Move ( ADR(RBuf^[3]), ADR ( FileName ), Len );
IF Len < SIZE(FileName) THEN
FileName[Len] := 0C
END;
ChoosePath(FileName); (* DIAG: FIX NEEDED TO USE DOWNLOAD PATH *)
TurnDisplayOn( FileName, CHR(RBuf^[1]) IN CHARSET{'D', 'R'} );
IF (RBuf^[1] = SHORTCARD('U')) THEN
SendFile (FileName)
ELSE
ReceiveFile (FileName)
END;
ShowTransferTime;
StopDisplay;
|'N': IF (CHR(RBuf^[1]) = 'G') AND (* May be GIF *)
(CHR(RBuf^[2]) = 'I') AND
(CHR(RBuf^[3]) = 'F') THEN
FileType := ' (GIF)';
TurnDisplayOn( GifTempName, TRUE );
ReceiveGIF;
StopDisplay;
RETURN
ELSE
SendFailure ('NUnknown packet type');
END;
|'+': DoTransportParameters;
|'F': ShowFailure;
ELSE SendFailure ('NUnknown packet type');
END; (* CASE *)
END; (* IF ReadPacket *)
ReleaseBufs;
END DleBSeen;
BEGIN (* Unit Initialization *)
AutoResume := FALSE;
SpecialQuoting := FALSE;
SpecialQuoteSet := DQextended;
UpdChk := DoBCks;
UseCRC := FALSE;
chkInit := 0;
AbortMax := 4;
DefPtr := ADR(Def);
END QCbplus.